package com.baseproject.datasource;

import android.support.annotation.NonNull;

import com.baseproject.datasource.get.procedure.DataProcedure;
import com.baseproject.datasource.local.LocalDataSource;
import com.baseproject.datasource.remote.RemoteDataSource;
import com.baseproject.util.LogUtils;

import java.io.File;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;

import rx.Observable;
import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Action1;
import rx.functions.Func1;
import rx.schedulers.Schedulers;

//import static com.google.common.base.Preconditions.checkNotNull;

/**
 * Concrete implementation to load tasks from the data sources into a cache.
 * <p>
 * For simplicity, this implements a dumb synchronisation between locally persisted data and data
 * obtained from the server, by using the remote data source only if the local database doesn't
 * exist or is empty.
 */
public class Repository implements DataSource {

    private static Repository INSTANCE = null;

    private final RemoteDataSource mRemoteDataSource;

    private final LocalDataSource mLocalDataSource;

    // Prevent direct instantiation.
    private Repository(@NonNull RemoteDataSource tasksRemoteDataSource,
                       @NonNull LocalDataSource tasksLocalDataSource) {
        mRemoteDataSource = tasksRemoteDataSource;
        mLocalDataSource = tasksLocalDataSource;
    }

    /**
     * Returns the single instance of this class, creating it if necessary.
     *
     * @param tasksRemoteDataSource the backend data source
     * @param tasksLocalDataSource  the device storage data source
     * @return the {@link Repository} instance
     */
    public static Repository getInstance(RemoteDataSource tasksRemoteDataSource,
                                         LocalDataSource tasksLocalDataSource) {
        if (INSTANCE == null) {
            synchronized (Repository.class) {
                if (INSTANCE == null) {
                    INSTANCE = new Repository(tasksRemoteDataSource, tasksLocalDataSource);
                }
            }
        }
        return INSTANCE;
    }

    public static Repository getInstance() {
        if (INSTANCE == null) {
            synchronized (Repository.class) {
                if (INSTANCE == null) {
                    INSTANCE = new Repository(RemoteDataSource.getInstance(), LocalDataSource.getInstance());
                }
            }
        }
        return INSTANCE;
    }

    /**
     * Used to force {getInstance(DataSource, DataSource)} to create a new instance next time it's called.
     */
    public static void destroyInstance() {
        INSTANCE = null;
    }

    Map<String, List> cacheList = new WeakHashMap<String, List>();

    public Map<String, List> getCacheList() {
        return cacheList;
    }

    /////////////////////////////////////getList/////////////////////////////////////////////////////

    public <T> Observable<List<T>> getList(DataProcedure dataProcedure, Class<T> className) {
        return dataProcedure.getList(className);
    }

    public <T> Observable<List<T>> getListFromLocalDataSource(DataProcedure dataProcedure, Class<T> className) {
        LogUtils.d("start local get! className = " + className.getSimpleName());
        return mLocalDataSource.getList(dataProcedure, className)
                .doOnNext(new Action1<List<T>>() {
                    @Override
                    public void call(List<T> contentList) {
                        LogUtils.d("local data used! className = " + className.getSimpleName());
                        if (null != contentList && contentList.size() > 0) {
                            cacheList.put(new String(dataProcedure.getUniqueKey()), contentList);
                        }
                    }
                });
    }

    public <T> Observable<List<T>> getListFromRemoteDataSource(DataProcedure dataProcedure, final Class<T> className) {
        LogUtils.d("start remote get! className = " + className.getSimpleName());
        return mRemoteDataSource.getList(dataProcedure, className)
                .doOnNext(new Action1<List<T>>() {
                    @Override
                    public void call(List<T> contentList) {
                        LogUtils.d("Remote data  used! className = " + className.getSimpleName());
                        if (null != contentList && contentList.size() > 0) {
                            cacheList.put(new String(dataProcedure.getUniqueKey()), contentList);
                        }
                    }
                });
    }

    /////////////////////////////////////getFile/////////////////////////////////////////////////////

    public Observable<File> getFile(final String url, final String destDir, final String destFileName) {
        return getFile(url, destDir, destFileName, false);
    }

    public Observable<File> getFile(final String url, final String destDir, final String destFileName, boolean cacheIsDirty) {
        if (cacheIsDirty) {
            return getFileFromRemoteDataSource(url, destDir, destFileName);
        } else {
            return mLocalDataSource.getFile(destDir, destFileName)
                    .flatMap(new Func1<File, Observable<File>>() {
                        @Override
                        public Observable<File> call(File file) {
                            if (file != null) {
                                LogUtils.d("local data used! file.getName() = " + file.getName());
                                return Observable.just(file);
                            } else {
                                return getFileFromRemoteDataSource(url, destDir, destFileName);
                            }
                        }
                    });
        }
    }

    @NonNull
    private Observable<File> getFileFromRemoteDataSource(String url, final String destDir, final String destFileName) {
        return mRemoteDataSource.downloadFile(url, destDir, destFileName)
                .map(new Func1<InputStream, File>() {
                    @Override
                    public File call(InputStream inputStream) {
                        return mLocalDataSource.saveFile(inputStream, destDir, destFileName);
                    }
                })
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread());
    }

    /////////////////////////////////////Post/////////////////////////////////////////////////////

    public Observable<String> httpPostRemote(String url, Map<String, String> params) {
        return mRemoteDataSource.httpPost(url, params);
    }

}
